﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEditor;
using UnityEditor.SceneManagement;

namespace PluginPathFinder 
{
    [CustomEditor(typeof(PathFinder))]
    public class PathFinderEditor : Editor 
    {
        enum SceneMode
        {
            AddNode,
            MoveNode,
            ConnectPath,
            None
        }

        private SceneMode sceneMode;

        private PathFinder script;

        private int selectedNodeForConnectNodesMode = -1;

        const string nodeGUITextColor = "#ff00ffff";
        const string pathGUITextColor = "#00ffffff";
        const string costGUITextColor = "#0000ffff";

        [MenuItem("GameObject/3dPath")]
       public static void Create3DPathFinder()
        {
            var managerGO = new GameObject("PathFinder");
            managerGO.AddComponent<PathFinder>();

        }

        #region inspector


        private bool showNodeIDsInTheScene = true;
        private bool showPathIDsInTheScene = true;
        private bool drawPathsInTheScene = true;
        private bool showCostsInTheScene = false;
        private bool showDefaultInspector = false;

        public override void OnInspectorGUI()
        {
            ShowNodesAndPathInInspector();
        }


        private void ShowNodesAndPathInInspector()
        {
            script.graphData.nodeSize = EditorGUILayout.Slider("Node gizmo Size", script.graphData.nodeSize, 0.1f, 3f);
            script.graphData.lineColor = EditorGUILayout.ColorField("Path Color", script.graphData.lineColor);
            script.graphData.lineType = (PathLineType)EditorGUILayout.EnumPopup("Path Type", script.graphData.lineType);
            script.graphData.heightFromTheGround = EditorGUILayout.FloatField("Offset from ground( Height )", script.graphData.heightFromTheGround);
            script.graphData.groundColliderLayerName = EditorGUILayout.TextField("Ground collider layer name", script.graphData.groundColliderLayerName);
            EditorGUILayout.Space();
            GUIStyle style = new GUIStyle();
            style.richText = true;
            GUILayout.Label("<size=12><b>Nodes</b></size>", style);

            if (script.graphData.nodes.Count > 0)
            {
                showNodeIDsInTheScene = EditorGUILayout.Toggle("Show Node IDs in scene", showNodeIDsInTheScene);

                List<Node> nodeList = script.graphData.nodes;
                for (int j = 0; j < nodeList.Count; j++)
                {
                    GUILayout.BeginHorizontal();
                    {
                        EditorGUILayout.LabelField("\t" + "Node <Color=" + nodeGUITextColor + ">" + nodeList[j].autoGeneratedID + "</Color>", style, GUILayout.Width(120f));

                        nodeList[j].SetPosition(EditorGUILayout.Vector3Field("", nodeList[j].Position));
                        EditorGUILayout.LabelField("Enable", EditorStyles.miniLabel, GUILayout.Width(50f));
                        nodeList[j].SetAsOpen(EditorGUILayout.Toggle(nodeList[j].IsOpen));

                        if (GUILayout.Button("+", GUILayout.Width(25f)))
                            AddNode(nodeList[j].Position + Vector3.right + Vector3.up, j + 1);
                        if (GUILayout.Button("-", GUILayout.Width(25f)))
                            DeleteNode(j);
                    }
                    GUILayout.EndHorizontal();
                }
            }
            else
            {
                EditorGUILayout.LabelField("<Color=green> Nodes are empty. Use <b>Add Node</b> in scene view to create Nodes!</Color>", style);
            }
            EditorGUILayout.Space();
            GUILayout.Label("<size=12><b>Paths</b></size>", style);

            if (script.graphData.paths.Count > 0)
            {
                showPathIDsInTheScene = EditorGUILayout.Toggle("Show Path IDs in scene", showPathIDsInTheScene);
                drawPathsInTheScene = EditorGUILayout.Toggle("Draw Paths", drawPathsInTheScene);
                showCostsInTheScene = EditorGUILayout.Toggle("Show Path Costs in scene", showCostsInTheScene);

                List<Path> paths = script.graphData.paths;
                for (int j = 0; j < paths.Count; j++)
                {
                    GUILayout.BeginHorizontal();
                    {
                        EditorGUILayout.LabelField("\t" + "Path <Color=" + pathGUITextColor + ">" + paths[j].autoGeneratedID + "</Color>", style, GUILayout.Width(120f));

                        EditorGUILayout.LabelField("From", EditorStyles.miniLabel, GUILayout.Width(30f)); paths[j].IDOfA = EditorGUILayout.IntField(paths[j].IDOfA, GUILayout.Width(50f));
                        EditorGUILayout.LabelField("To", EditorStyles.miniLabel, GUILayout.Width(25f)); paths[j].IDOfB = EditorGUILayout.IntField(paths[j].IDOfB, GUILayout.Width(50f));
                        EditorGUILayout.LabelField("<Color=" + costGUITextColor + ">" + "Cost" + "</Color>", style, GUILayout.Width(30f)); paths[j].cost = EditorGUILayout.IntField(paths[j].cost, GUILayout.Width(50f));

                        EditorGUILayout.LabelField("One Way", EditorStyles.miniLabel, GUILayout.Width(50f)); paths[j].isOneWay = EditorGUILayout.Toggle(paths[j].isOneWay);
                        EditorGUILayout.LabelField("Enable", EditorStyles.miniLabel, GUILayout.Width(50f)); paths[j].isOpen = EditorGUILayout.Toggle(paths[j].isOpen);

                        if (GUILayout.Button("+", GUILayout.Width(25f)))
                            AddPath(j + 1);
                        if (GUILayout.Button("-", GUILayout.Width(25f)))
                            DeletePath(j);
                    }
                    GUILayout.EndHorizontal();
                }
            }
            else
            {
                EditorGUILayout.LabelField("<Color=green> Paths are empty. Use <b>Connect Nodes</b> in scene view to create Paths!</Color>", style);
            }

            /*
            if (GUI.changed)
                MarkThisDirty();
            */
        }
        #endregion

        #region scenegui
        private void OnSceneGUI()
        {
            //添加
            int controlid = GUIUtility.GetControlID(FocusType.Passive);
            HandleUtility.AddDefaultControl(controlid);

            //添加窗口
            DrawGUIWindowOnScene();

            UpdateMouseInput();

            if (sceneMode == SceneMode.AddNode)
            {
                DrawNodes(Color.green);
            }
            else if (sceneMode == SceneMode.MoveNode)
            {
                DrawNodes(Color.magenta, true);
            }
            else if (sceneMode == SceneMode.ConnectPath)
            {
               
                DrawNodes(Color.green, false, script.graphData.GetNode(selectedNodeForConnectNodesMode), Color.red);
            }
            else
            {

                DrawNodes(Color.gray);
            }

            DrawPathLine();
            CheckGUIChanged();
        }

        private void CheckGUIChanged()
        {
            if (GUI.changed)
            {
                SceneView.RepaintAll();
            }
        }
        private void DrawGUIWindowOnScene() 
        {
            GUILayout.Window(1, new Rect(0f, 20f, 70f, 80f), delegate(int windowID)
                 {
                     GUILayout.BeginHorizontal();
                     sceneMode = (SceneMode)GUILayout.SelectionGrid((int)sceneMode, new string[] { "Add Node", "Move Node", "Connect Node", "None" }, 1);
                     Debug.Log(sceneMode.ToString());
                     GUI.color = Color.white;
                     GUILayout.EndHorizontal();
                 }, "Mode");

            GUILayout.Window(2, new Rect(0f, 155f, 70f, 80f), delegate (int windowID)
                 {
                     EditorGUILayout.BeginVertical();
                     if(GUILayout.Button("Delete Last Node"))
                     {
                         DeleteNode();
                     }
                     if(GUILayout.Button("Delete Last Path"))
                     {
                         DeletePath();
                     }
                     if(GUILayout.Button("Clear All"))
                     {
                         ClearNode();
                         ClearPath();
                     }
                     if(GUILayout.Button("Refresh Data"))
                     {
                         script.graphData.ReGenerateIDs();
                     }
                     GUI.color = Color.white;

                     EditorGUILayout.EndVertical();
                 }, "");
        }
        #endregion

        #region scenegui
        public void UpdateMouseInput()
        {
            Event e = Event.current;
           
            if(e.type == EventType.MouseDown)
            {
                if(e.button ==0)
                {
                    onMouseClick(e.mousePosition);
                }
            }
            else if (e.type == EventType.MouseUp)
            {
                MarkThisDirty();
                SceneView.RepaintAll();
            }
        }

        public void OnEnable()
        {
            sceneMode = SceneMode.None;
            script = target as PathFinder;
        }

        private void DeleteNode(int removeIndex = -1)
        {
            List<Node> nodeList = script.graphData.nodes;
            if (nodeList == null || nodeList.Count == 0)
                return;

            if (removeIndex == -1)
                removeIndex = nodeList.Count - 1;

            Node nodeRemoved = nodeList[removeIndex];
            nodeList.RemoveAt(removeIndex);
            script.graphData.ReGenerateIDs();
        }

        void DeletePath(int removeIndex = -1)
        {
            List<Path> pathList = script.graphData.paths;
            if (pathList == null || pathList.Count == 0)
                return;

            if (removeIndex == -1)
                removeIndex = pathList.Count - 1;

            Path removedPath = pathList[removeIndex];
            pathList.RemoveAt(removeIndex);
            script.graphData.ReGenerateIDs();
        }

        private void ClearNode()
        {
            script.graphData.nodes.Clear();
        }

        private void ClearPath()
        {
            script.graphData.paths.Clear();
        }

        private void onMouseClick(Vector2 mousePos)
        {
            if (sceneMode == SceneMode.AddNode)
            {
                LayerMask layermask = 1 << LayerMask.NameToLayer("Default");
                Ray ray = HandleUtility.GUIPointToWorldRay(mousePos);

                RaycastHit hit;
                if (Physics.Raycast(ray, out hit, 100000f, layermask))
                {
                    AddNode(hit.point);
                }
            }
            else if (sceneMode == SceneMode.ConnectPath)
            {
                LayerMask backgroundLayerMask = 1 << LayerMask.NameToLayer(script.graphData.groundColliderLayerName);
                Ray ray = HandleUtility.GUIPointToWorldRay(mousePos);
                RaycastHit hit;

                if (Physics.Raycast(ray, out hit, 1000f, backgroundLayerMask))
                {
                   
                    Vector3 hitPos = hit.point;
                    TryAddPath(hitPos);
                }
            }
        }

        private void AddNode(Vector3 hitPosition,int addIndex = -1)
        {
            Node node = new Node(hitPosition);
            if (addIndex == -1)
                script.graphData.nodes.Add(node);
            else
                script.graphData.nodes.Insert(addIndex, node);

            script.graphData.ReGenerateIDs();

        }

        private void TryAddPath(Vector3 position)
        {
            var id = script.FindNearestNode(position);
            Node selectNode = script.graphData.GetNode(script.FindNearestNode(position));
            if(selectNode == null)
            {
                return;
            }

            if(selectedNodeForConnectNodesMode != -1)
            {
                Debug.Log("selected Add Path");
                AddPath(-1, selectedNodeForConnectNodesMode, selectNode.autoGeneratedID);
                selectedNodeForConnectNodesMode = -1;
            } 
            else
            {
                selectedNodeForConnectNodesMode = selectNode.autoGeneratedID;
            }
        }

        private void AddPath(int addIndex = -1,int from = -1,int to = -1)
        {

            if(from != -1 && to != -1)
            {
                if(from == to)
                {
                    return;
                }
                Path pd = script.graphData.GetPathBetween(from,to);
                if(pd != null)
                {
                    return;
                }
            }

            Path newPath = new Path(from, to);
            if (addIndex == -1)
                script.graphData.paths.Add(newPath);
            else
                script.graphData.paths.Insert(addIndex, newPath);
            script.graphData.ReGenerateIDs();
        }

       private void MarkThisDirty()
        {
            if (Application.isPlaying)
                return;

            if (PrefabUtility.GetCorrespondingObjectFromSource(script.gameObject) != null)
            {
                //PathFinderEditor.Logger.LogInfo ( "Prefab for PathFinder found! Marked it Dirty ( Modified )");
                EditorUtility.SetDirty(script);
            }
            else
            {
                //PathFinderEditor.Logger.LogInfo ( "Prefab for PathFinder Not found! Marked the scene as Dirty ( Modified )");
                EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene());
            }
        }
        #endregion

        #region nodedraw
        private void DrawNodes(Color color, bool canMove = false, Node selectedNode = null, Color colorForSelected = default(Color))
        {
            Handles.color = color;
            foreach(var node in script.graphData.nodes)
            {
                if (selectedNode != null && node == selectedNode)
                    Handles.color = colorForSelected;
                else
                    Handles.color = color;
                if(canMove)
                {
                    node.SetPosition(Handles.FreeMoveHandle(node.Position, Quaternion.identity, script.graphData.nodeSize, Vector3.zero, Handles.SphereHandleCap));
                }
                Handles.SphereHandleCap(0, node.Position, Quaternion.identity, script.graphData.nodeSize, EventType.Repaint);

                Handles.color = Color.white;
                DrawGUIDisplayForNodes();
                Handles.color = Color.white;
            }
        }

        private void DrawPathLine()
        {
            List<Path> paths = script.graphData.paths;
            List<Node> nodes = script.graphData.nodes;
            Vector3 currNode;
            Vector2 guiPosition;

            if (paths == null || nodes == null)
                return;

            Handles.color = script.graphData.lineColor;
            Node a, b;

            for (int i = 0; i < paths.Count; i++)
            {
                if (!paths[i].isOpen)
                    continue;

                a = b = null;
                if (script.graphData.nodesSorted.ContainsKey(paths[i].IDOfA))
                    a = script.graphData.nodesSorted[paths[i].IDOfA];

                if (script.graphData.nodesSorted.ContainsKey(paths[i].IDOfB))
                    b = script.graphData.nodesSorted[paths[i].IDOfB];

                if (a != null && b != null && a != b && a.IsOpen && b.IsOpen)
                {
                    if (drawPathsInTheScene)
                        Handles.DrawLine(a.Position, b.Position);

                    Handles.BeginGUI();
                    {
                        currNode = (a.Position + b.Position) / 2;
                        guiPosition = HandleUtility.WorldToGUIPoint(currNode);
                        string str = "";
                        if (showPathIDsInTheScene)
                            str += "<Color=" + pathGUITextColor + ">" + paths[i].autoGeneratedID.ToString() + "</Color>";
                        if (showCostsInTheScene)
                        {
                            if (!string.IsNullOrEmpty(str))
                                str += "<Color=" + "#ffffff" + ">" + "  Cost: " + "</Color>";
                            str += "<Color=" + costGUITextColor + ">" + paths[i].cost.ToString() + "</Color>";
                        }

                        if (!string.IsNullOrEmpty(str))
                        {
                            GUIStyle style = new GUIStyle();
                            style.richText = true;
                            GUI.Label(new Rect(guiPosition.x - 10, guiPosition.y - 30, 40, 20), str,style);
                        }
                           
                    }
                    Handles.EndGUI();
                }
            }
            Handles.color = Color.white;
        }

        private void DrawGUIDisplayForNodes()
        {
            if (!showNodeIDsInTheScene)
                return;

            Node currNode;
            Vector2 guiPosition;
            GUIStyle style = new GUIStyle();
            style.richText = true;

            Handles.BeginGUI();

            for (int i = 0; i < script.graphData.nodes.Count; i++)
            {
                currNode = script.graphData.nodes[i];
                guiPosition = HandleUtility.WorldToGUIPoint(currNode.Position);
                GUI.Label(new Rect(guiPosition.x - 10, guiPosition.y - 30, 20, 20), "<Color=" + nodeGUITextColor + ">" + currNode.autoGeneratedID.ToString() + "</Color>", style);
            }
            Handles.EndGUI();
        }
        #endregion
    }
}
